% JS2TS - convert JavaScript to TypeScript using type inference
% James Cordy, Huawei Technologies
% May 2022 (Rev. Sep 2023)

% Usage:  txl app.js js2ts.txl [-d VERBOSE] [-d TRACE] [-d EXPNTYPES] [-d NOUNIONS] [-d TSSTYLE]

% Unified TypeScript/JavaScript grammar
include "bom.grm"
include "js-ts.grm"

% Grammar overrides
include "overrides.grm"


% Main rule 

function main
    % The file path of the main input JS file, used to find imports
    import TXLinput [stringlit]
    export MainFile [stringlit]
        TXLinput
    % Apply the steps of the JS2TS process
    replace [program]
        P [program]
    by
        P [expandAndNormalize]          % expand imports, normalize language
          [inferDeclarations]           % infer local and global variable declarations
          [inferTypes]                  % add type annotations and infer types
          [unexpandAndCleanup]          % unexpand imports, unnormalize language, improve style 
          [progress "Done."]
          [progress ""]
end function


% Stage 1: Expand imports, normalize language

function expandAndNormalize
    replace [program]
        P [program]
    by
        P % Expand source of imported files
          [progress "Expanding imports"] 
          [expandImports] 
          [trace "INLINED IMPORTS"]

          % Rename TS keywords
          [progress "Renaming keywords"] 
          [renameKeywords] 
          [trace "RENAMED KEYWORDS"]

          % Fix JS literals
          [progress "Converting literals"] 
          [convertLiterals] 
          [trace "CONVERTED LITERALS"]

          % Normalize JS expressions
          [progress "Normalizing expressions"] 
          [normalizeExpressions] 
          [trace "EXPRESSIONS NORMALIZED"]

          % Normalize JS statements
          [progress "Normalizing statements"] 
          [normalizeStatements] 
          [trace "STATEMENTS NORMALIZED"]

          % Normalize function declarations
          [progress "Normalizing functions"] 
          [normalizeFunctions] 
          [trace "FUNCTIONS NORMALIZED"]
end function

% Expand/unexpand import statements with a copy of their source
include "expand-imports.rul"

% Rename TS keywords
include "rename-keywords.rul"

% Convert JS literals
include "convert-literals.rul"

% Normalize JS expressions
include "normalize-expressions.rul"

% Normalize JS statements
include "normalize-statements.rul"

% Normalize function declarations 
include "normalize-functions.rul"


% Stage 2: Infer declarations for local and global variables

function inferDeclarations
    replace [program]
        P [program]
    by
        P
          % Uniqueify variable names
          [progress "Renaming variables"] 
          [renameVariables]
          [trace "VARIABLE RENAMING"]

          % Infer undeclared variables
          [progress "Inferring variables"] 
          [inferVariables]
          [trace "VARIABLE INFERENCE"]
end function

% Infer missing variables from references
include "infer-variables.rul"

% Uniquely rename local variables 
include "rename-variables.rul"


% Stage 3: Add type annotations and infer types

function inferTypes
    replace [program]
        P [program]
    by
        P
          % Initialize built-in type tables
          [progress "Initializing built-in types"] 
          [initializeBuiltinStaticTypes] 
          [initializeBuiltinCallTypes] 
          [trace "INITIALIZED BUILT-IN TYPES"]

          % Fully parenthesize expressions to assist in type inference
          [progress "Parenthesizing expressions"] 
          [parenthesizeExpressions] 
          [trace "PARENTHESIZED EXPRESSIONS"]

          % Add default type annotations
          [progress "Adding expression type annotations"] 
          [addExpressionTypeAnnotations]
          [progress "Adding default types"] 
          [defaultTypes]
          [trace "DEFAULT TYPES"]

          % Infer literal types
          [progress "Infer literal types"]
          [literalTypes]
          [trace "LITERAL TYPES"]

          % Infer new object types
          [progress "Infer new object types"]
          [newObjectTypes]
          [trace "NEW OBJECT TYPES"]

          % Infer built-in types
          [progress "Infer built-in types"]   
          [builtinStaticTypes]
          [trace "BUILT-IN TYPES"]

          % Type inference to transitive closure
          [progress "Type inference"]   
          [typeInference]
          [trace "INFERRED TYPES"]

          % Decide on final function and other types (once, when others done)
          [progress "Choose final types"]         
          [finalReturnTypes]
          [finalTypes]
          [trace "FINAL TYPES"]

        #ifndef EXPNTYPES
          % Remove expression type annotations
          [progress "Removing expression type annotations"]
          [removeExpressionTypeAnnotations]
          [unparenthesizeExpressions]
          #ifndef RENAMES
          [unrenameVariables]
          #endif
          [trace "REMOVED EXPRESSION TYPES"]
        #endif
end function

% Fully parenthesize/unparenthesize expressions
include "parenthesize-expressions.rul"

% Assign default type annotations to expressions and variables
include "default-types.rul"

% Make final decisions on type annotations 
include "final-types.rul"

% Type inference to transitive closure fixed point

rule typeInference
    % Keep track of number of iterations
    export Iteration [number]
        0

    % Next iteration
    replace [program]
        P [program]
    import Iteration
    export Iteration
        Iteration [+ 1] 
    construct IterationMessage [stringlit]
        _ [+ "Iteration "] [+ Iteration]

    #if not NOLIMIT then
        % Limit to ten iterations
        deconstruct not Iteration
            11
    #end if

    % Apply inference rules
    construct NewP [program]
        P 
          [progress IterationMessage]
          [progress "    Cast types"]           [castTypes]
          [progress "    This types"]           [thisTypes]
          [progress "    Dynamic object types"] [dynamicObjectTypes]
          [progress "    Computed array types"] [computedArrayLiteralTypes]
          [progress "    Initializer types"]    [propagateInitializerTypes]
          [progress "    Property member types"] [propertyMemberClosureTypes]
          [progress "    Variable types"]       [propagateVariableTypes]
          [progress "    Assignment types"]     [assignmentTypes]
          [progress "    Equality op types"]    [equalityOpTypes]
          [progress "    Arithmetic op types"]  [arithmeticOpTypes]
          [progress "    Relational op types"]  [relationalOpTypes]
          [progress "    Conditional op types"] [conditionalOpTypes]
          [progress "    Logical op types"]     [logicalOpTypes]
          [progress "    Parenthesized types"]  [parenthesizedExpressionTypes]
          [progress "    Closure types"]        [closureTypes]
          [progress "    Function types"]       [functionTypes]
          [progress "    Function call types"]  [callTypes]
          [progress "    Builtin call types"]   [builtinCallTypes]
          [progress "    Formal parameter types"] [formalTypes]
          [progress "    Argument types"]       [argumentTypes]
          [progress "    Return types"]         [returnTypes]

    % Have we reached a fixed point?
    deconstruct not NewP
        P

    % No, so new result
    construct IterationTitle [stringlit]
        _ [+ "ITERATION "] [unparse Iteration]
    by
        NewP [trace IterationTitle]
end rule

% Type inference rules -
% Warning! Every type inference is an approximation, 
% and may not be accurate due to JS type equivalence rules.

% Type merging rules, used everywhere
include "merge-types.rul"

% Infer literal expression types
include "literal-types.rul"

% Infer explicit cast expression types
include "cast-types.rul"

% Infer arithmetic expression types
include "arithmetic-types.rul"

% Infer conditional expression types
include "conditional-types.rul"

% Infer parenthesized expression types
include "parenthesized-types.rul"

% Infer logical expression types
include "logical-types.rul"

% Infer relational expression types
include "relational-types.rul"

% Infer equality expression types
include "equality-types.rul"

% Infer new object expression types
include "newobject-types.rul"

% Infer dynamic object expression types
include "dynamic-types.rul"

% Infer this object expression types
include "thisobject-types.rul"

% Transitive closure of known types
include "closure-types.rul"

% Transitive closure of assignment types
include "assignment-types.rul"

% Infer function formal parameter types
include "formal-types.rul"

% Infer function return types
include "return-types.rul"

% Infer function expression types
include "function-types.rul"

% Infer function argument types
include "argument-types.rul"

% Infer built-in static member types
include "builtin-static-types.rul"

% Transitive closure of property types
include "property-types.rul"

% Transitive closure of variable types
include "variable-types.rul"

% Infer initializer types
include "initializer-types.rul"

% Infer function call types
include "call-types.rul"
include "builtin-call-types.rul"


% Stage 4: Unexpand imports, unnormalize language, improve style 

function unexpandAndCleanup
    replace [program]
        P [program]
    by
        P
        #ifndef NOUNEXPAND
          % Unexpand inlined imports
          [progress "Unexpanding imports"]
          [unexpandImports]
          [trace "UNINLINED IMPORTS"]
        #endif

          % convert import/export paths
          [progress "Convert import/export paths"]
          [convertImportExportPaths]
          [trace "CONVERTED IMPORT/EXPORT PATHS"]

          % Unnormalize expressions
          [progress "Unnormalizing expressions"]
          [unnormalizeExpressions]
          [trace "UNNORMALIZED EXPRESSIONS"]

          % Unnormalize JS statements
          [progress "Unnormalizing statements"] 
          [unnormalizeStatements] 
          [trace "UNNORMALIZED STATEMENTS"]

          % Simplify and improve TS style
          [progress "Removing redundant type annotations"]
          [removeRedundantTypeAnnotations]
          [trace "REMOVED REDUNDANT TYPES"]

          % Add JS2TS auxiliary types
          [progress "Add js2ts types"]         
          [addDynamicTypeDefinition]
        #ifdef NOUNEXPAND
          [noUnexpandImports]
        #endif
          [trace "ADDED JS2TS TYPES"]
end function

% Remove redundant type annotations 
include "redundant-types.rul"


% Tracing utilities

% Optionally output progress messages
function progress Message [stringlit]
    match [program]
        P [program]
  #ifdef TRACE
    #define VERBOSE
  #endif
  #ifdef VERBOSE
    construct _ [id]
        _ [message Message]
  #endif
end function

% Show progress after each step
function trace Title [stringlit]
    match [program]
        P [program]
  #ifdef TRACE
    construct TitleHeader [stringlit]
        _ [+ "[H[J--- "] [+ Title] 
    construct Print [program]
        P [message TitleHeader]
          [print]
          [breakpoint]
  #endif
end function
